using System.Collections.Generic;
using System.Linq.Expressions;
using System.Web.UI;
using System.Web.UI.WebControls;
using System;

namespace PropertyExpression.TypedDataBinding.Web
{
    public class RepeaterBinder<TData> : CollectionBinder<TData, Repeater>
    {
        protected override bool IsDataContainer(Control control)
        {
            return control is RepeaterItem;
        }

        protected override BaseControlWrapper CreateBaseControlWrapper(Repeater control)
        {
            return new BaseControlWrapperImpl(control);
        }

        private class BaseControlWrapperImpl : BaseControlWrapper
        {
            private readonly Repeater repeater;
            public BaseControlWrapperImpl(Repeater repeater)
            {
                this.repeater = repeater;
            }

            public override event Action<IBaseControlItemEventArgs> ItemDataBound
            {
                add
                {
                    AddItemDataBoundHandler(value, () => repeater.ItemDataBound += repeater_ItemDataBound);
                }
                remove
                {
                    RemoveItemDataBoundHandler(value, () => repeater.ItemDataBound -= repeater_ItemDataBound);
                }
            }

            private void repeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
            {
                ItemDataBoundAction(new BaseControlItemEventArgs(e));
            }

            public override event Action<IBaseControlItemEventArgs> ItemCreated
            {
                add
                {
                    AddItemCreatedHandler(value, () => repeater.ItemCreated += repeater_ItemCreated);
                }
                remove
                {
                    RemoveItemCreatedHandler(value, () => repeater.ItemCreated -= repeater_ItemCreated);
                }
            }

            private void repeater_ItemCreated(object sender, RepeaterItemEventArgs e)
            {
                ItemCreatedAction(new BaseControlItemEventArgs(e));
            }

            private class BaseControlItemEventArgs : IBaseControlItemEventArgs
            {
                private readonly RepeaterItemEventArgs repeaterItemEventArgs;
                public BaseControlItemEventArgs(RepeaterItemEventArgs repeaterItemEventArgs)
                {
                    this.repeaterItemEventArgs = repeaterItemEventArgs;
                }

                public Control Item
                {
                    get { return repeaterItemEventArgs.Item; }
                }

                public object DataItem
                {
                    get { return repeaterItemEventArgs.Item.DataItem; }
                }

                public int ItemIndex
                {
                    get { return repeaterItemEventArgs.Item.ItemIndex; }
                }
            }

            public override object DataSource
            {
                set { repeater.DataSource = value; }
            }

            public override void DataBind()
            {
                repeater.DataBind();
            }
        }
    }

    public class PairRepeaterBinder<TData>
    {
        public readonly RepeaterBinder<TData> RepeaterBinder 
            = new RepeaterBinder<TData>();

        private readonly RepeaterBinder<Dto<TData>> dtoRepeaterBinder 
            = new RepeaterBinder<Dto<TData>>();

        public PairGenericBinder<TControl, TData> AddForKey<TControl>(
            TControl control) where TControl : Control
        {
            return new PairGenericBinder<TControl, TData>(
                dtoRepeaterBinder.Add(control),
                RepeaterBinder.Add(control));
        }

        public void ToModel(Control control, Func<Dto<TData>, TData> func)
        {
            var dto = new Dto<TData>();
            dtoRepeaterBinder.ToModel(control, dto);
            RepeaterBinder.ToModel(control, func(dto));
        }
    }

    public class PairGenericBinder<TControl, TData> where TControl : Control
    {
        private readonly GenericBinder<TControl, Dto<TData>> genericBinder;
        private readonly GenericBinder<TControl, TData> genericBinder2;

        public PairGenericBinder(GenericBinder<TControl, Dto<TData>> genericBinder,
            GenericBinder<TControl, TData> genericBinder2)
        {
            this.genericBinder = genericBinder;
            this.genericBinder2 = genericBinder2;
        }

        public PairGenericBinder<TControl, TData> Add<TControlValue, TDataValue>(
            Expression<Func<TControl, TControlValue>> controlExpression,
            Expression<Func<TData, TDataValue>> dataExpression)
        {
            genericBinder2.Add(controlExpression, dataExpression);
            genericBinder.AddProperty(
                controlExpression,
                x => new PairPropertyDelegate<TDataValue>(x, dataExpression)
                );
            return this;
        }

        private class PairPropertyDelegate<TDataValue>: IPropertyDelegate<TDataValue>
        {
            private readonly Dto<TData> dto;
            private readonly Expression<Func<TData, TDataValue>> dataExpression;

            public PairPropertyDelegate(
                Dto<TData> dto,
                Expression<Func<TData, TDataValue>> dataExpression)
            {
                this.dto = dto;
                this.dataExpression = dataExpression;
            }

            public TDataValue Value
            {
                get
                {
                    return dto.GetValue(dataExpression);
                }
                set
                {
                    dto.SetValue(dataExpression, value);
                }
            }
        }
    }

    public class Dto<TData>
    {
        private readonly Dictionary<string, object> dictionary 
            = new Dictionary<string, object>();

        public void SetValue<TDataValue>(
            Expression<Func<TData, TDataValue>> dataExpression,
            TDataValue value)
        {
            dictionary[GetPropertyName(dataExpression)] = value;
        }

        private static string GetPropertyName<TDataValue>(
            Expression<Func<TData, TDataValue>> dataExpression)
        {
            return ((MemberExpression) dataExpression.Body).Member.Name;
        }

        public TDataValue GetValue<TDataValue>(
            Expression<Func<TData, TDataValue>> dataExpression)
        {
            return (TDataValue) dictionary[GetPropertyName(dataExpression)];
        }
    }
}